package org.testis.utils.tree;

import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
import java.util.LinkedList;

/**
 * 
 * @author Cahit Emir Erel
 */
public abstract class TreeNode implements Transferable {

    final public static DataFlavor TREE_NODE_FLAVOR = new DataFlavor(
	    TreeNode.class, "Tree Node Information");
    private static DataFlavor m_flavors[] = { TREE_NODE_FLAVOR };
    private String m_name = "";
    private Object m_object = null;
    private TreeNode m_parent = null;
    private LinkedList<TreeNode> m_children = new LinkedList<TreeNode>();
    private Integer m_place = null;
    private boolean m_isSelected;

    /**
     * Constructs an empty TreeNode
     */
    public TreeNode() {
    }

    /**
     * Constructs a new TreeNode with the given parameters
     * 
     * @param name
     *            - Name of the TreeNode
     * @param object
     *            - object reference
     * @param place
     *            - place of the node in its parent's children list
     */
    public TreeNode(String name, Object object, Integer place) {
	setName(name);
	setObject(object);
	setPlace(place);
    }

    /**
     * Returns place of the node in its parent's children list
     * 
     * @return place of the node in its parent's children list
     */
    public Integer getPlace() {
	return m_place;
    }

    /**
     * Sets the place of the node in its parent's children list
     * 
     * @param place
     *            - place of the node in its parent's children list
     */
    public void setPlace(Integer place) {
	if (place != null && place < 0) {
	    place = 0;
	}
	m_place = place;
    }

    /**
     * Returns the name of the TreeNode
     * 
     * @return name of the TreeNode
     */
    public String getName() {
	return m_name;
    }

    /**
     * Sets the name of the TreeNode
     * 
     * @param name
     *            - name of the TreeNode
     */
    public void setName(String name) {
	if (name == null) {
	    name = "";
	}
	m_name = name;
    }

    /**
     * Returns the object reference
     * 
     * @return object reference
     */
    public Object getObject() {
	return m_object;
    }

    /**
     * Sets the object reference
     * 
     * @param object
     *            - object reference
     */
    protected void setObject(Object object) {
	m_object = object;
    }

    /**
     * Returns the parent of this TreeNode
     * 
     * @return parent of this TreeNode
     */
    public TreeNode getParent() {
	return m_parent;
    }

    /**
     * Sets the parent of this TreeNode
     * 
     * @param parent
     *            - parent of this TreeNode
     */
    public void setParent(TreeNode parent) {
	m_parent = parent;
    }

    /**
     * Returns the children list of this TreeNode
     * 
     * @return children list of this TreeNode
     */
    public TreeNode[] getChildren() {
	return getChildrenLinkedList().toArray(
		new TreeNode[getChildrenLinkedList().size()]);
    }

    /**
     * Returns the children list of this TreeNode in a LinkedList object
     * 
     * @return children list of this TreeNode in a LinkedList object
     */
    public LinkedList<TreeNode> getChildrenLinkedList() {
	return m_children;
    }

    /**
     * Returns the nth child of this TreeNode
     * 
     * @param n
     *            - place of child in the children list
     * @return nth child of this TreeNode
     */
    public TreeNode getChild(int n) {
	if (n < 0 || n >= getChildrenLinkedList().size()) {
	    return null;
	} else {
	    return getChildrenLinkedList().get(n);
	}
    }

    /**
     * Sets the children list of this TreeNode
     * 
     * @param children
     *            - children of this TreeNode to be added
     */
    public void setChildren(TreeNode[] children) {
	deleteAllChildren();
	if (children != null && children.length > 0) {
	    for (int i = 0; i < children.length; i++) {
		addChild(children[i]);
	    }
	}
    }

    /**
     * Add a child node to the children list of this TreeNode
     * 
     * @param node
     *            - child of this TreeNode to be added
     */
    public void addChild(TreeNode node) {
	if (node != null) {
	    if (!isContainsChild(node)) {
		int childCount = getChildrenLinkedList().size();
		if (childCount > 0) {
		    getChildrenLinkedList().add(childCount, node);
		} else {
		    getChildrenLinkedList().add(0, node);
		}
		node.setParent(this);
	    } else {
		int getCurrentIndex = getChildIndex(node);
		if (getChildrenLinkedList().size() != getCurrentIndex) {
		    getChildrenLinkedList().remove(getCurrentIndex);
		    addChild(node);
		}
	    }
	}
    }

    /**
     * Add a child node to the nth spot of the children list of this TreeNode n
     * should be less than the number of children currently in the list and
     * greater than or equal to 0.
     * 
     * @param n
     *            - place of child in the children list you want it to be added
     * @param node
     *            - child of this TreeNode to be added
     */
    public void addChild(int n, TreeNode node) {
	if (node != null && n >= 0 && n <= getChildrenLinkedList().size()
		&& !isContainsChild(node)) {
	    getChildrenLinkedList().add(n, node);
	    node.setParent(this);
	}
    }

    /**
     * child index of the given TreeNode
     * 
     * @param node
     *            - child of this TreeNode to be deleted
     */
    abstract protected int getChildIndex(TreeNode node);

    /**
     * return true if the given TreeNode is in children
     * 
     * @param node
     *            - child of this TreeNode to be deleted
     */
    abstract protected boolean isContainsChild(TreeNode node);

    /**
     * Delete child of this TreeNode
     * 
     * @param node
     *            - child of this TreeNode to be deleted
     */
    abstract public void deleteChild(TreeNode node);

    /**
     * Delete the nth child of this TreeNode
     * 
     * @param n
     *            - place of child in the children list to be deleted
     * @return child that is deleted
     */
    public TreeNode deleteChild(int n) {
	if (n >= 0 && n < getChildrenLinkedList().size()) {
	    TreeNode node = getChildrenLinkedList().remove(n);
	    node.setParent(null);
	    return node;
	} else {
	    return null;
	}
    }

    /**
     * Deletes all children of this TreeNode
     */
    private void deleteAllChildren() {
	while (getChildrenLinkedList().size() > 0) {
	    getChildrenLinkedList().removeFirst().setParent(null);
	}
    }

    /**
     * Deletes all children of this TreeNode hierarchy
     */
    private void purgeAllChildren() {
	TreeNode node;
	while (getChildrenLinkedList().size() > 0) {
	    node = getChildrenLinkedList().removeFirst();
	    if (node != null) {
		node.setParent(null);
		node.purgeAllChildren();
	    }
	}
    }

    @Override
    public String toString() {
	StringBuffer buffer = new StringBuffer();
	buffer.append(getName());
	if (getObject() != null) {
	    buffer.append(getObject().toString());
	}
	return buffer.toString();
    }

    /**
     * Returns true if this TreeNode has any children
     * 
     * @return true if children count of this TreeNode is greater than 0
     */
    public boolean hasChildren() {
	if (getChildrenLinkedList().size() > 0) {
	    return true;
	} else {
	    return false;
	}
    }

    // --------- Transferable --------------
    public boolean isDataFlavorSupported(DataFlavor df) {
	if (df == null) {
	    return false;
	} else {
	    return df.equals(TREE_NODE_FLAVOR);
	}
    }

    /** implements Transferable interface */
    public Object getTransferData(DataFlavor df)
	    throws UnsupportedFlavorException, IOException {
	if (df != null && df.equals(TREE_NODE_FLAVOR)) {
	    return this;
	} else {
	    throw new UnsupportedFlavorException(df);
	}
    }

    /** implements Transferable interface */
    public DataFlavor[] getTransferDataFlavors() {
	return m_flavors;
    }

    // --------- Serializable --------------
    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
	out.defaultWriteObject();
    }

    private void readObject(java.io.ObjectInputStream in) throws IOException,
	    ClassNotFoundException {
	in.defaultReadObject();
    }

    /**
     * @return the m_isSelected
     */
    public boolean isSelected() {
	return m_isSelected;
    }

    /**
     * @param m_isSelected
     *            the m_isSelected to set
     */
    public void setSelected(boolean isSelected) {
	this.m_isSelected = isSelected;
    }
}
